﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ScriptCompare
{
    public class ScriptMethod
    {
        public const string[] MethodKeywords = { "sub", "function", "property" };

        private string _methodName = "";
        private readonly List<ScriptLine> _methodComments = new List<ScriptLine>();
        private readonly List<ScriptLine> _methodBody = new List<ScriptLine>();
        private readonly List<ScriptLine> _methodBodyComments = new List<ScriptLine>();

        protected ScriptMethod(IList<ScriptLine> bodyText, int startPos)
        {
            // Sanity check
            if (startPos >= bodyText.Count || startPos == 0)
                throw new InvalidOperationException("Identified a ScriptBody method at an impossible position.");

            // Read the method signature
            string methodName = ExtractMethodName(bodyText[startPos].LineText);

            // Did we find a method name
            if (String.IsNullOrEmpty(methodName))
            {
                throw new Exception("Could not find Method Name for method body.");
            }

            // Read upwards for any commments
            int commentReadPos = startPos - 1;

            while (commentReadPos-- > 0)
            {
                ScriptLine cLine = bodyText[commentReadPos];

                if (!cLine.IsComment)
                    break;
                else
                    _methodComments.Add(cLine);
            }

            // If there were comments found, reverse the list
            if (_methodComments.Count > 0)
                _methodComments.Reverse();

            // Start reading in the method body
            foreach (ScriptLine line in bodyText.LinesAfter(startPos + 1))
            {
                // Check for terminating line
                if (IsTerminatingLine(line.LineText))
                    break;
                // Check for comments
                else if (line.IsComment)
                    _methodBodyComments.Add(line);
                // Everything else
                else
                    _methodBody.Add(line);
            }

        }

        public string MethodName
        {
            get { return _methodName; }
        }

        public List<ScriptLine> CompareToMethod(ScriptMethod source)
        {
            List<ScriptLine> differences = new List<ScriptLine>();

            // Check comments first
            foreach (ScriptLine comment in _methodComments)
            {
                if (!source._methodComments.Contains(comment))
                {
                    differences.Add(comment);
                }
            }

            // Now check the actual body of the method
            var sourceLines = source.GetComparableLines();
            var targetLines = this.GetComparableLines();

            int sourceMax = sourceLines.Count;
            int targetMax = targetLines.Count;

            int largestSetLines = (sourceMax > targetMax) ? sourceMax : targetMax;

            ScriptLine? sourceLine = null;
            ScriptLine? targetLine = null;
            for (int i = 0; i < largestSetLines; i++)
            {
                if (i < sourceMax) { sourceLine = sourceLines[i]; }
                if (i < targetMax) { targetLine = targetLines[i]; }

                if (targetLine != null)
                {
                    if ((sourceLine == null) || (!sourceLine.Value.Matches(targetLine.Value)))
                    {
                        differences.Add(targetLine.Value);
                    }
                }
            }

            // Return differences
            return (differences);
        }

        public List<ScriptLine> EntireMethodBody
        {
            get
            {
                List<ScriptLine> entireBody = new List<ScriptLine>();

                entireBody.AddRange(_methodBody);
                entireBody.AddRange(_methodBodyComments);

                entireBody.Sort(LineUtility.LineSortComparer);

                entireBody.InsertRange(0, _methodComments);

                return (entireBody);
            }
        }

        private IList<ScriptLine> GetComparableLines()
        {
            return (_methodBody.Where(mb => !mb.IsComment && !mb.IsEmpty).ToList());
        }

        private static bool IsTerminatingLine(string line)
        {
            bool isTerminatingLine = false;
            string[] tokens = LineUtility.TokenizeLine(line);
            int lastPosPostion = tokens.Length - 2;

            for (int i = 0; (i < tokens.Length) && !isTerminatingLine; i++)
            {
                string t1 = tokens[i];

                // Check for end and remaining tokens
                if (t1 == "end" && (i < lastPosPostion))
                {
                    // Get the next token
                    string t2 = tokens[i + 1];

                    // Check for method keywords
                    foreach (string methKey in MethodKeywords.ToList())
                    {
                        if (methKey.ToLower() == t2)
                        {
                            isTerminatingLine = true;
                            break;
                        }
                    }
                }               
            }

            return (isTerminatingLine);
        }

        private static string ExtractMethodName(string signature)
        {
            string methName = "";

            string[] tokens = LineUtility.TokenizeLine(signature, '(');
            methName = LineUtility.FindTokenAfterString(tokens, MethodKeywords);

            return (methName);
        }

        
    }
}
